﻿using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using XUCore.Serializer;
using XUCore.WeChat.Apis.Sns;

namespace XUCore.WeChat.App
{
    /// <summary>  
    /// 微信小程序用户数据的签名验证和解密  
    /// </summary>  
    public class WeChatMiniProgramDecrypt : IWeChatMiniProgramDecrypt
    {
        private WeChatFuncs weChatFuncs;
        private ISnsApi snsApi;

        public WeChatMiniProgramDecrypt(WeChatFuncs weChatFuncs, ISnsApi snsApi)
        {
            this.weChatFuncs = weChatFuncs;
            this.snsApi = snsApi;
        }

        #region 获取OpenId和SessionKey的Json数据包

        /// <summary>  
        /// 反序列化包含OpenId和SessionKey的Json数据包
        /// </summary>
        /// <param name="loginInfo">Json数据包</param>
        /// <returns>包含OpenId和SessionKey的类</returns>
        public async Task<WeChatOpenIdAndSessionKey> DecodeOpenIdAndSessionKey(WeChatLoginInfo loginInfo)
        {
            // WxOpenIdAndSessionKey oiask = GetOpenIdAndSessionKeyString(loginInfo.code).ToObject<WxOpenIdAndSessionKey>();
            var options = weChatFuncs?.GetMiniProgramOptions(loginInfo.App);

            WeChatOpenIdAndSessionKey oiask = await snsApi.GetJsCode2SessionAsync(options.AppId, options.AppSecret, loginInfo.Encryption.Code);

            if (!String.IsNullOrEmpty(oiask.ErrCode))
                throw new Exception($"获取SessionKey失败，{oiask.ErrCode} {oiask.ErrMsg}");
            return oiask;
        }

        #endregion

        #region 验证用户信息

        /// <summary>  
        /// 根据微信小程序平台提供的签名验证算法验证用户发来的数据是否有效  
        /// </summary>  
        /// <param name="rawData">公开的用户资料</param>  
        /// <param name="signature">公开资料携带的签名信息</param>  
        /// <param name="sessionKey">从服务端获取的SessionKey</param>  
        /// <returns>True：资料有效，False：资料无效</returns>  
        public bool VaildateUserInfo(string rawData, string signature, string sessionKey)
        {
            //创建SHA1签名类  
            SHA1 sha1 = SHA1.Create();
            //编码用于SHA1验证的源数据  
            byte[] source = Encoding.UTF8.GetBytes(rawData + sessionKey);
            //生成签名  
            byte[] target = sha1.ComputeHash(source);
            //转化为string类型，注意此处转化后是中间带短横杠的大写字母，需要剔除横杠转小写字母  
            string result = BitConverter.ToString(target).Replace("-", "").ToLower();

            //比对，输出验证结果  
            return signature == result;
        }

        /// <summary>  
        /// 根据微信小程序平台提供的签名验证算法验证用户发来的数据是否有效  
        /// </summary>  
        /// <param name="loginInfo">登陆信息</param>  
        /// <param name="sessionKey">从服务端获取的SessionKey</param>  
        /// <returns>True：资料有效，False：资料无效</returns>  
        public bool VaildateUserInfo(WeChatLoginInfo loginInfo, string sessionKey)
        {
            return VaildateUserInfo(loginInfo.Verify.RawData, loginInfo.Verify.Signature, sessionKey);
        }

        /// <summary>  
        /// 根据微信小程序平台提供的签名验证算法验证用户发来的数据是否有效  
        /// </summary>  
        /// <param name="loginInfo">登陆信息</param>  
        /// <param name="idAndKey">包含OpenId和SessionKey的类</param>  
        /// <returns>True：资料有效，False：资料无效</returns>  
        public bool VaildateUserInfo(WeChatLoginInfo loginInfo, WeChatOpenIdAndSessionKey idAndKey)
        {
            return VaildateUserInfo(loginInfo, idAndKey.Session_Key);
        }

        #endregion

        #region 解密微信用户信息

        /// <summary>  
        /// 根据微信小程序平台提供的解密算法解密数据  
        /// </summary>  
        /// <param name="encryptedData">加密数据</param>  
        /// <param name="iv">初始向量</param>  
        /// <param name="sessionKey">从服务端获取的SessionKey</param>  
        /// <returns></returns>  
        public WeChatUserInfo DecryptForInfo(string encryptedData, string iv, string sessionKey)
        {
            WeChatUserInfo userInfo;
            //创建解密器生成工具实例  
            var aes = Aes.Create();
            //设置解密器参数  
            aes.Mode = CipherMode.CBC;
            aes.BlockSize = 128;
            aes.Padding = PaddingMode.PKCS7;
            //格式化待处理字符串  
            byte[] byte_encryptedData = Convert.FromBase64String(encryptedData);
            byte[] byte_iv = Convert.FromBase64String(iv);
            byte[] byte_sessionKey = Convert.FromBase64String(sessionKey);

            aes.IV = byte_iv;
            aes.Key = byte_sessionKey;
            //根据设置好的数据生成解密器实例  
            ICryptoTransform transform = aes.CreateDecryptor();

            //解密  
            byte[] final = transform.TransformFinalBlock(byte_encryptedData, 0, byte_encryptedData.Length);

            //生成结果  
            string result = Encoding.UTF8.GetString(final);

            //反序列化结果，生成用户信息实例  
            userInfo = result.ToObject<WeChatUserInfo>();

            return userInfo;
        }

        /// <summary>
        /// 根据微信小程序平台提供的解密算法解密数据，推荐直接使用此方法，需要传入 loginInfo.rawData, loginInfo.signature 进行信息校验
        /// </summary>
        /// <param name="loginInfo">登陆信息</param>  
        /// <returns>用户信息</returns>  
        public async Task<WeChatUserInfo> DecryptForInfo(WeChatLoginInfo loginInfo)
        {
            if (loginInfo == null)
                return null;

            if (string.IsNullOrEmpty(loginInfo.Encryption.Code))
                return null;

            WeChatOpenIdAndSessionKey oiask = await DecodeOpenIdAndSessionKey(loginInfo);

            if (oiask == null)
                return null;

            if (!VaildateUserInfo(loginInfo, oiask))
                return null;

            WeChatUserInfo userInfo = DecryptForInfo(loginInfo.Encryption.EncryptedData, loginInfo.Encryption.Iv, oiask.Session_Key);

            if (userInfo == null) return null;

            userInfo.SessionInfo = oiask;

            if (string.IsNullOrEmpty(userInfo.OpenId)) userInfo.OpenId = oiask.OpenId;
            if (string.IsNullOrEmpty(userInfo.UnionId)) userInfo.UnionId = oiask.UnionId;

            return userInfo;
        }

        #endregion

        #region 解密微信手机信息

        /// <summary>  
        /// 微信小程序解密手机号码 根据微信小程序平台提供的解密算法解密数据，推荐直接使用此方法  
        /// </summary>  
        /// <param name="loginInfo">登陆信息</param>  
        /// <returns>用户信息</returns>  
        public async Task<WeChatMobileInfo> DecryptForPhone(WeChatLoginInfo loginInfo)
        {
            if (loginInfo == null)
                return null;

            if (String.IsNullOrEmpty(loginInfo.Encryption.Code))
                return null;

            WeChatOpenIdAndSessionKey oiask = await DecodeOpenIdAndSessionKey(loginInfo);

            if (oiask == null)
                return null;

            WeChatMobileInfo userInfo = DecryptForPhone(loginInfo.Encryption.EncryptedData, loginInfo.Encryption.Iv, oiask.Session_Key);

            if (userInfo == null) return null;

            userInfo.SessionInfo = oiask;

            if (string.IsNullOrEmpty(userInfo.OpenId)) userInfo.OpenId = oiask.OpenId;
            if (string.IsNullOrEmpty(userInfo.UnionId)) userInfo.UnionId = oiask.UnionId;

            return userInfo;
        }

        /// <summary>
        /// 微信小程序解密手机号码 开放数据校验与解密 加密数据解密算法
        /// </summary>
        public WeChatMobileInfo DecryptForPhone(string encryptedData, string iv, string sessionKey)
        {
            return DecryptForWeChatApplet(encryptedData, iv, sessionKey).ToObject<WeChatMobileInfo>();
        }

        /// <summary>
        /// 微信小程序 开放数据解密
        /// AES解密（Base64）
        /// </summary>
        /// <param name="encryptedData">已加密的数据</param>
        /// <param name="sessionKey">解密密钥</param>
        /// <param name="iv">IV偏移量</param>
        /// <returns></returns>
        private string DecryptForWeChatApplet(string encryptedData, string iv, string sessionKey)
        {
            var decryptBytes = Convert.FromBase64String(encryptedData);
            var keyBytes = Convert.FromBase64String(sessionKey);
            var ivBytes = Convert.FromBase64String(iv);
            var outputBytes = DecryptByAesBytes(decryptBytes, keyBytes, ivBytes);
            return Encoding.UTF8.GetString(outputBytes);
        }

        #region AES加密

        /// <summary>
        /// AES加密
        /// </summary>
        /// <param name="encryptedBytes">待加密的字节数组</param>
        /// <param name="keyBytes">加密密钥字节数组</param>
        /// <param name="ivBytes">IV初始化向量字节数组</param>
        /// <param name="cipher">运算模式</param>
        /// <param name="padding">填充模式</param>
        /// <returns></returns>
        private byte[] EncryptToAesBytes(byte[] encryptedBytes, byte[] keyBytes, byte[] ivBytes,
            CipherMode cipher = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
        {
            if (encryptedBytes == null || encryptedBytes.Length <= 0)
                throw new ArgumentNullException(nameof(encryptedBytes));
            if (keyBytes == null || keyBytes.Length <= 0)
                throw new ArgumentNullException(nameof(keyBytes));
            if (ivBytes == null || ivBytes.Length <= 0)
                throw new ArgumentNullException(nameof(ivBytes));

            var des = Aes.Create();

            des.Key = keyBytes;
            des.IV = ivBytes;
            des.Mode = cipher;
            des.Padding = padding;

            var outputBytes = des.CreateEncryptor().TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
            return outputBytes;
        }

        #endregion

        #region AES解密

        /// <summary>
        /// AES解密
        /// </summary>
        /// <param name="decryptedBytes">待解密的字节数组</param>
        /// <param name="keyBytes">解密密钥字节数组</param>
        /// <param name="ivBytes">IV初始化向量字节数组</param>
        /// <param name="cipher">运算模式</param>
        /// <param name="padding">填充模式</param>
        /// <returns></returns>
        private byte[] DecryptByAesBytes(byte[] decryptedBytes, byte[] keyBytes, byte[] ivBytes,
            CipherMode cipher = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
        {
            if (decryptedBytes == null || decryptedBytes.Length <= 0)
                throw new ArgumentNullException(nameof(decryptedBytes));
            if (keyBytes == null || keyBytes.Length <= 0)
                throw new ArgumentNullException(nameof(keyBytes));
            if (ivBytes == null || ivBytes.Length <= 0)
                throw new ArgumentNullException(nameof(ivBytes));

            var des = Aes.Create();

            des.Key = keyBytes;
            des.IV = ivBytes;
            des.Mode = cipher;
            des.Padding = padding;

            var outputBytes = des.CreateDecryptor().TransformFinalBlock(decryptedBytes, 0, decryptedBytes.Length);
            return outputBytes;
        }

        #endregion

        #endregion
    }
}
